package model.game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import javax.swing.JPanel;

import model.cell.AbstractCell;
import model.cell.EmptyCell;
import model.cell.tower.AbstractTower;
import model.cell.tower.LaserTower;
import model.cell.tower.RocketTower;
import model.cell.tower.UnavailableCellDecorator;
import model.cell.tower.LightningTower;
import model.player.Player;


/**
 * This class manages all game board features such as the map, the path
 * and enemy waves.
 * 
 * @author alexandre.altun
 */
public class Board extends JPanel implements MouseListener {
	
	private static final long serialVersionUID = 1L;

	private int _cellSize;
	
	private Dimension _mapDim;
	
	private Path _path;
	
	private ArrayList<ArrayList<AbstractCell>> _map;
	
	private EnemyWave _enemyWave;
	
	private Player _player;
	
	private ArrayList<AbstractTower> _towers = new ArrayList<AbstractTower>();
	
	// Map buffer
	Image imageStoredMap;
	Graphics bufferStoredMap;
	
	// Game buffer
	Image imageGame;
	Graphics bufferGame;
	
	private BoardThread _boardThread = new BoardThread();
	
	private int _currentWave = 1;
	
	
	
	public Board(Player player) {
		
		this.setBackground(Color.BLACK);
		_player = player;
		this.addMouseListener(this);
	}
	
	
	/**
	 * Initialize the board with the map passed as parameter.
	 * 
	 * @param map
	 */
	public void init(Map map) {
		
		_cellSize = map.getCellSize();
		_mapDim = map.getMapDim();
		_path = map.getPath();
		
		this.setPreferredSize(new Dimension(_mapDim.width * _cellSize, _mapDim.height * _cellSize));
		
		buildMap();
		_enemyWave = new EnemyWave(_currentWave, _path, _player);
		addTower(4, 3);
	}
	
	
	/**
	 * Start the game. 
	 * It launches threads that move enemies and repaint the board. 
	 */
	public void start() {
		
		_enemyWave.start();
		_boardThread.start();
	}
	
	
	public boolean running() {
		
		if (_enemyWave.waveCleared()) {
			
			// TODO if false then the party is over
			_enemyWave.buildNextWave();
			
			return true;
		}

		return true;
	}
	
	
	public void buildMap() {
		
		Dimension dim = new Dimension(_cellSize, _cellSize);
		_map = new ArrayList<ArrayList<AbstractCell>>();
		
		for (int i=0; i<_mapDim.height; i++){
			ArrayList<AbstractCell> row = new ArrayList<AbstractCell>();
			for (int j=0; j<_mapDim.width; j++){
				row.add(new EmptyCell(new Point(_cellSize*j,_cellSize*i), dim));
			}
			_map.add(row);
		}
		
		repaint();
	}
	
	
	public void addTower(int x, int y) {
		
		//_towers.add(new LaserTower(new Point(_cellSize*x,_cellSize*y), new Dimension(_cellSize, _cellSize), _enemyWave));
		//_towers.add(new RocketTower(new Point(_cellSize*5,_cellSize*6), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new LightningTower(new Point(_cellSize*5,_cellSize*6), new Dimension(_cellSize, _cellSize), _enemyWave));
		
		/*
		_towers.add(new RocketTower(new Point(_cellSize*3,_cellSize*1), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new RocketTower(new Point(_cellSize*3,_cellSize*2), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new RocketTower(new Point(_cellSize*3,_cellSize*3), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new RocketTower(new Point(_cellSize*3,_cellSize*4), new Dimension(_cellSize, _cellSize), _enemyWave));
		//*/
		
		/*
		_towers.add(new RocketTower(new Point(_cellSize*4,_cellSize*1), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new RocketTower(new Point(_cellSize*4,_cellSize*2), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new RocketTower(new Point(_cellSize*4,_cellSize*3), new Dimension(_cellSize, _cellSize), _enemyWave));
		_towers.add(new RocketTower(new Point(_cellSize*4,_cellSize*4), new Dimension(_cellSize, _cellSize), _enemyWave));
		//*/
	}
	
	
	private void _drawTowers(Graphics g) {
		
		for (Iterator<AbstractTower> it = _towers.iterator(); it.hasNext();) {
			AbstractTower tower = (AbstractTower) it.next();
			tower.draw(g);
		}
	}
	
	
	private void _drawTowerBullets(Graphics g) {
		
		for (Iterator<AbstractTower> it = _towers.iterator(); it.hasNext();) {
			AbstractTower tower = (AbstractTower) it.next();
			tower.drawBullets(g);
		}
	}
	
	
	/**
	 * Paint the whole map (map + path + towers) in a buffer. 
	 * This buffer allows us to repaint quickly the background of the board.
	 * 
	 * This method is called at the first paint event and when a tower is either added or deleted.
	 */
	public void paintStoredMap() {
		
		imageStoredMap = createImage(_mapDim.width * _cellSize, _mapDim.height * _cellSize);
		bufferStoredMap = imageStoredMap.getGraphics();
		
		// Draw map
		for (Iterator<ArrayList<AbstractCell>> iterCol = _map.iterator(); iterCol.hasNext();) {
			ArrayList<AbstractCell> row = (ArrayList<AbstractCell>) iterCol.next();
			for (Iterator<AbstractCell> iterRaw = row.iterator(); iterRaw.hasNext();) {
				AbstractCell cell = (AbstractCell) iterRaw.next();
				cell.draw(bufferStoredMap);
			}
		}
		
		// Draw grid
		for (int i=1; i<_mapDim.height; i++){
			Color c = bufferStoredMap.getColor();
			bufferStoredMap.setColor(Color.gray);
			bufferStoredMap.drawLine(0, i * _cellSize, _mapDim.width * _cellSize, i * _cellSize);
			bufferStoredMap.setColor(c);
		}
		for (int i=1; i<_mapDim.width; i++){
			Color c = bufferStoredMap.getColor();
			bufferStoredMap.setColor(Color.gray);
			bufferStoredMap.drawLine(i * _cellSize, 0, i * _cellSize, _mapDim.height * _cellSize);
			bufferStoredMap.setColor(c);
		}
		
		_path.draw(bufferStoredMap);
	}
	
	
	/**
	 * Draw the game buffer onto the panel buffer.
	 * 
	 * It first erases game buffer with map buffer and then draw all game elements 
	 * over it.
	 */
	public void paintComponent(Graphics g) {
		
		if (null == imageStoredMap) {
			paintStoredMap();
		}
		
		if (null == imageGame) {
			createGameBuffer();
		}
		
		// Erase buffer with map
		bufferGame.drawImage(imageStoredMap, 0, 0, this);
		_drawTowers(bufferGame);
		_enemyWave.drawEnemies(bufferGame);
		_drawTowerBullets(bufferGame);
		
		_drawMouseCursor(bufferGame);
		
		g.drawImage(imageGame, 0, 0, this);
	}
	
	
	private void _drawMouseCursor(Graphics g) {
		
		Point mouseGridPos = _getGridPos(getMousePosition());
		AbstractTower tower = MouseCursor.getTower();
		
		if (mouseGridPos == null || tower == null) return;
		
		tower.drawArea();
		tower.setRectangle(new Rectangle(new Point(mouseGridPos.x * _cellSize, mouseGridPos.y * _cellSize), new Dimension(_cellSize, _cellSize)));
		
		if (!_cellEmpty(mouseGridPos)) {
			// Cell is unavailable. Draw red aura.
			tower = new UnavailableCellDecorator(tower);
		}
		
		tower.draw(g);
	}

	
	private Point _getGridPos(Point pos) {
		
		if (pos == null) return null;
		
		return new Point((int) (pos.x / _cellSize), (int) (pos.y / _cellSize));
	}
	
	
	private boolean _cellEmpty(Point gridPos) {
		
		for (Iterator<AbstractTower> it = _towers.iterator(); it.hasNext();) {
			AbstractTower tower = (AbstractTower) it.next();
			Point towerGridPos = new Point(tower.getGridPosX(), tower.getGridPosY());
			if (gridPos.equals(towerGridPos)) 
				return false;
		}
		
		LinkedList<AbstractCell> pathList = _path.getPath();
		
		for (Iterator<AbstractCell> it = pathList.iterator(); it.hasNext();) {
			AbstractCell cell = (AbstractCell) it.next();
			Point cellGridPos = new Point(cell.getGridPosX(), cell.getGridPosY());
			if (gridPos.equals(cellGridPos)) 
				return false;
		}
		
		return true;
	}
	

	/**
	 * Create the game buffer if it doesn't exists.
	 */
	public void createGameBuffer() {
		
		imageGame = createImage(_mapDim.width * _cellSize, _mapDim.height * _cellSize);
		bufferGame = imageGame.getGraphics();
	}

	
	public void mouseClicked(MouseEvent e) {
		
		
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mousePressed(MouseEvent e) {
		
		for (Iterator<AbstractTower> it = _towers.iterator(); it.hasNext();) {
			AbstractTower tower = (AbstractTower) it.next();
			Rectangle towerRect = tower.getRectangle();
			
			if ( towerRect.x <= e.getX() && towerRect.x + towerRect.width > e.getX() && towerRect.y < e.getY() && towerRect.y + towerRect.height > e.getY() ) {
				tower.drawArea();
				paintStoredMap();
			}
		}
	}

	public void mouseReleased(MouseEvent e) {
		
		Point mouseGridPos = _getGridPos(getMousePosition());
		AbstractTower tower = MouseCursor.getTower();
		
		if (mouseGridPos == null || tower == null) return;
		
		tower.setRectangle(new Rectangle(new Point(mouseGridPos.x * _cellSize, mouseGridPos.y * _cellSize), new Dimension(_cellSize, _cellSize)));
		
		if (!_cellEmpty(mouseGridPos)) {
			
			MouseCursor.setTower(null);
			return;
		}
		
		tower.setEnemyWave(_enemyWave);
		MouseCursor.setTower(null);
		//tower.start();
		
		_towers.add(tower);
		return;
	}
	
	
	
	/**
	 * Thread that's in charge of repainting the board every 10ms
	 * 
	 * @author alexandre.altun
	 */
	class BoardThread extends Thread {

		public void run() {
			while(running()) {
				try {
					repaint();
					//DebugPanel.getInstance().incFps();
					sleep( 10 );
				} catch ( Exception e ) {} 
			}
		}
	}
	
}
